#include "crossbit/ExecutionEngine.h"
#include "arch/x86/X86Translator.h"
#include "arch/mips/MIPSSyscallHandler.h"
#include "arch/mips/MIPSProcessorState.h"
#include "crossbit/Exit.h"
#include "crossbit/Profiler.h"
#include "crossbit/VFilter.h"
#include "vector"
#include "map"
#include "algorithm"
using namespace std;
using namespace crossbit;

//#define DEBUG
#define LINK
//#define CALCULATE
#define MAX_NUM (300)

#ifdef CALCULATE
struct BLOCK_INFO {
    XTMemAddr addr;
    long long count;
};
struct EDGE_INFO {
    XTMemAddr from;
    XTMemAddr to;
    long long count;
};
bool cmp_1( BLOCK_INFO a , BLOCK_INFO b ) {
    return a.count > b.count;
}
bool cmp_2( EDGE_INFO a , EDGE_INFO b ) {
    return a.count > b.count;
}
#endif

void ExecutionEngine::run()
{
    ee_memory_image->load();
    ee_interpreter->initStack( ee_memory_image->stack() );

    XTMemAddr entry = ee_memory_image->entry();

    TBlock* pre_tblock = NULL;
    VBlock* pre_vblock = NULL;
    GLOBAL::BUILD_SUPER_BLOCK = 0;

	UTIL::XTSigfillset(&GLOBAL::SIG_FULL_SET);
	UTIL::XTSigemptyset(&GLOBAL::SIG_ON_SET);
	UTIL::XTSigemptyset(&GLOBAL::SIG_ON_OLDSET);

    //XTMemAddr gp = 0 ;	//FIXME: obsolete ?

#ifdef CALCULATE
    map< XTMemAddr , long long > block_count;
    map< long long , long long > branch_count;
#endif
    GLOBAL::switch_addr = ee_memory_manager -> allocateExecutable( 8 );
    ((X86Translator*)ee_translator) -> set_switch_point( GLOBAL::switch_addr );	

    GLOBAL::sig_ret_addr = ee_memory_manager -> allocateExecutable( 24 );
    ((X86Translator*)ee_translator) -> set_sigret_point( GLOBAL::sig_ret_addr );

    GLOBAL::hashtable_dispatch = ee_memory_manager -> allocateExecutable(64);
    ((X86Translator*)ee_translator) -> set_hashtable_dispatch( GLOBAL::hashtable_dispatch );	

    // allocate 1M executable space for the exit stub;
    GLOBAL::exit_stub_base = ee_memory_manager -> allocateExecutable( GLOBAL::EXIT_STUB_SIZE );
    GLOBAL::exit_stub_end = GLOBAL::exit_stub_base + GLOBAL::EXIT_STUB_SIZE;

    // allocate 1M executable space for the super stub;
    GLOBAL::super_stub_base = ee_memory_manager -> allocateExecutable( GLOBAL::SUPER_STUB_SIZE );
    GLOBAL::super_stub_end = GLOBAL::super_stub_base + GLOBAL::SUPER_STUB_SIZE;

	ALL_SIG_OFF

    while (1) {

#ifdef DEBUG
        //				ee_interpreter->get_state()->dump();
        //printf("The entry is 0x%x\n",entry);
#endif

        TBlock *tblock = ee_tcache -> lookupTBlock(entry);

#ifdef CALCULATE
        block_count[entry]++;
#endif
        VBlock *vblock;
        if (tblock == NULL) {
            ee_builder->getReady(entry);
            XTMemAddr next = entry;

            while( !ee_builder->isEnd() )
                next = ee_interpreter -> decode(next, ee_builder);
            vblock = ee_builder -> wrapUp(next);

            VFilter::runFilters(vblock);
           	vblock -> setEndOfCall( GLOBAL::checkCall( vblock ) );

            tblock = ee_tcache -> requestTBlock();
            ee_translator -> encode(vblock, tblock);    
            ee_tcache -> submitTBlock(entry,tblock,vblock);

            while( GLOBAL::return_stack.size() != 0 ) {
                std::pair<XTMemAddr , XTMemAddr> return_pair = GLOBAL::return_stack.top() ;
                GLOBAL::return_stack.pop();
                XTMemAddr mem_addr = return_pair.first;
                XTMemAddr return_addr = return_pair.second;
                TBlock *temp_tblock = ee_tcache -> lookupTBlock( return_addr ) ;
                if( temp_tblock == NULL ) {
                    VBlock *temp_vblock;
                    ee_builder -> getReady( return_addr );
                    XTMemAddr next = return_addr;

                    while( !ee_builder->isEnd() )
                        next = ee_interpreter -> decode(next, ee_builder);
                    temp_vblock = ee_builder -> wrapUp(next);

                    VFilter::runFilters(temp_vblock);
                    temp_vblock -> setEndOfCall( GLOBAL::checkCall( temp_vblock ) );

                    temp_tblock = ee_tcache -> requestTBlock();
                    ee_translator -> encode(temp_vblock, temp_tblock);    
                    ee_tcache -> submitTBlock(return_addr,temp_tblock,temp_vblock);
                }
                *((XTMemAddr*)mem_addr) = temp_tblock -> enterAddr();
            }					 

        }
        XTMemAddr enter = tblock->enterAddr();

#ifdef LINK

        if( pre_tblock != NULL ){
            Exit* exit = GLOBAL::getExit(GLOBAL::last_exit);

            pre_vblock = ee_tcache -> lookupVBlock( pre_tblock -> getSPC() ); 
            if( pre_vblock -> isEndOfCall() ) {
                ee_trace_manager -> callLinkTo( pre_tblock , GLOBAL::last_exit , tblock );
            } else if( exit -> link_type == LinkType::DIRECT ){
                ee_trace_manager -> directLinkTo( pre_tblock , GLOBAL::last_exit , tblock );
            }
            else if( exit -> link_type == LinkType::INDIRECT ){
                ee_trace_manager -> indirectLinkTo( pre_tblock , GLOBAL::last_exit , tblock );
            }
        }
#endif
		ALL_SIG_ON
        CONTEXT_SAVE
            __asm__ __volatile__(
                    "mov %%esp , %%eax\n\t"
                    "sub $0x4, %%eax\n\t"
                    "mov %%eax , %0\n\t"
                    :
                    :"m"(GLOBAL::saved_esp));  // save the current esp to the GLOBAL::save_esp;
        ((void (*)()) enter) ();
        __asm__ __volatile__(
                "mov %%ebx , %0\n\t"
                "mov %%edx , %1\n\t"
                :
                :"m"(GLOBAL::last_exit) , "m"(GLOBAL::next_address));
        CONTEXT_RESTORE
		ALL_SIG_OFF

            if( GLOBAL::BUILD_SUPER_BLOCK ){
                // printf("build super_block\n");
                GLOBAL::tblock_stub = (TBlock *) GLOBAL::BUILD_SUPER_BLOCK; 
                GLOBAL::BUILD_SUPER_BLOCK = 0;
                entry = GLOBAL::tblock_stub -> getSPC();
                TBlock* former_tblock = ee_tcache -> lookupTBlock( entry );
                VBlock *super_vblock = Profiler::buildSuperVBlock( ee_tcache , entry ); 	
                if( super_vblock == NULL ){
                    super_vblock = ee_tcache -> lookupVBlock( entry );
                }
                TBlock *super_tblock = ee_tcache -> requestTBlock();
                super_tblock -> setSuperBlock();
                ee_translator -> encode( super_vblock , super_tblock ); 

                ee_tcache -> submitTBlock( entry , super_tblock , super_vblock );						
                ee_trace_manager -> linkSuperBlock( former_tblock , super_tblock );
                pre_tblock = NULL;	
                while( GLOBAL::return_stack.size() != 0 ) {
                    std::pair<XTMemAddr , XTMemAddr> return_pair = GLOBAL::return_stack.top() ;
                    GLOBAL::return_stack.pop();
                    XTMemAddr mem_addr = return_pair.first;
                    XTMemAddr return_addr = return_pair.second;
                    TBlock *temp_tblock = ee_tcache -> lookupTBlock( return_addr ) ;
                    if( temp_tblock == NULL ) {
                        VBlock *temp_vblock;
                        ee_builder -> getReady( return_addr );
                        XTMemAddr next = return_addr;

                        while( !ee_builder->isEnd() )
                            next = ee_interpreter -> decode(next, ee_builder);
                        temp_vblock = ee_builder -> wrapUp(next);

                        temp_tblock = ee_tcache -> requestTBlock();
                        ee_translator -> encode(temp_vblock, temp_tblock);    
                        ee_tcache -> submitTBlock(return_addr,temp_tblock,temp_vblock);
                    }
                    *((XTMemAddr*)mem_addr) = temp_tblock -> enterAddr();
                }					 
                continue;
            }

        Exit *exit = GLOBAL::getExit(GLOBAL::last_exit);
        GLOBAL::tblock_stub = exit -> tblock_pointer;
        if(  exit -> link_type == LinkType::INDIRECT ){
            exit -> next = GLOBAL::next_address;
            VBlock * temp_vblock = ee_tcache -> lookupVBlock( GLOBAL::tblock_stub -> getSPC() );
            temp_vblock -> setVbNextSpc( GLOBAL::next_address); 
        }

        if (exit -> why == ExitReason::SYSCALLEXIT) {
#ifdef CALCULATE
            if( ((MIPSSyscallHandler*)ee_syscall_handler) -> syscode() == 4246 ){
                FILE *output = fopen("block_count" , "w");
                XTUint32 block_size = block_count.size();
                vector<BLOCK_INFO> block_info(block_size);
                map<XTMemAddr , long long> :: iterator it = block_count.begin();
                XTInt32 i = 0;
                while( it != block_count.end() ){
                    block_info[i].addr = (*it).first;
                    block_info[i].count = (*it).second;
                    it++;
                    i++;
                }
                sort( block_info.begin(), block_info.end(), cmp_1 );
                for( int i = 0 ; i < block_size ; i++ ) {
                    fprintf(output , "block address : 0x%x  runtime count : %lld\n" , block_info[i].addr , block_info[i].count);
                }

                fclose( output );
                output = fopen("edge_count" , "w");
                XTUint32 edge_size = branch_count.size();
                vector<EDGE_INFO> edge_info(edge_size);
                map<long long , long long> :: iterator eit = branch_count.begin();
                i = 0;
                while( eit != branch_count.end() ){
                    edge_info[i].from = (XTMemAddr)((*eit).first >> 32);
                    edge_info[i].to = (XTMemAddr)((*eit).first);
                    edge_info[i].count = (*eit).second;
                    eit++;
                    i++;
                }
                sort( edge_info.begin(), edge_info.end(), cmp_2 );
                for( int i = 0 ; i < edge_size ; i++ ) {
                    fprintf(output , "edge from : 0x%x  to : 0x%x  runtime count : %lld\n" , edge_info[i].from , edge_info[i].to , edge_info[i].count);
                }


                fclose( output );
            }
#endif

            (*ee_syscall_handler)();
        } 

#ifdef CALCULATE
        branch_count[(((long long)entry) << 32) | exit -> next] ++;
#endif
        entry = exit -> next;

        pre_tblock = GLOBAL::tblock_stub;
    }

	ALL_SIG_ON
}

